#include "smart_pointer.h"
#include "smart_pointer.cc"
#include <thread>

// 实现一个智能指针
// 使用智能指针来释放pa
void Test1()
{
    try
    {
        int *pa = new int;
        SmartPointer<int> ptr(pa);
        throw std::bad_alloc();
        std::cout << "delete pa" << std::endl;
        delete pa;
    }
    catch (const std::exception &e)
    {
        std::cout << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown error!" << std::endl;
    }
}

// 一个问题：智能指针可以被拷贝吗？
// 答案是不可以的，因为智能指针的拷贝是浅拷贝，一旦拷贝发生那么同一块空间会被析构两次
void Test2()
{
    try
    {
        int *pa = new int;
        SmartPointer<int> ptr1(pa);
        // 浅拷贝
        SmartPointer<int> ptr2(ptr1);
        // 赋值
        SmartPointer<int> ptr3(nullptr);
        ptr3 = ptr2;

        throw std::bad_alloc();
        std::cout << "delete pa" << std::endl;
        delete pa;
    }
    catch (const std::exception &except)
    {
        std::cout << except.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown error!" << std::endl;
    }
}

// 所以我们应该对智能指针的拷贝和赋值进行处理
// 所以提出了auto_ptr(非常不建议使用)，它的本质是管理权转移
// 但是这是一个大坑，我们无法知道一个智能指针内部的原生指针是否为nullptr，一旦对该指针赋值，后果很糟糕
void Test3()
{
    try
    {
        int *pa = new int;
        AutoPointer<int> ptr1(pa);
        // 拷贝构造
        // AutoPointer<int> ptr2(ptr1);
        AutoPointer<int> ptr3(nullptr);
        ptr3 = ptr1;

        // 假设我现在不知道ptr1已经变成空指针，我给它进行赋值操作
        *ptr1 = 10; // 直接发生段错误

        throw std::bad_alloc();
        delete pa;
    }
    catch (const std::exception &except)
    {
        std::cout << except.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown error!" << std::endl;
    }
}

// 在上面的例子中，可以得出auto_ptr是一个大坑，所以大家一致都不愿意去使用它
// C++11中推出了unique_ptr, shared_ptr, weak_ptr
// (根据boost库中的scoped_ptr, shared_ptr, weak_ptr添加)
// unique_ptr在auto_ptr的基础上进行改造，直接不允许拷贝赋值
// unique_ptr
void Test4()
{
    try
    {
        int *pa = new int;
        UniquePointer<int> ptr1(pa);
        // 拷贝
        // UniquePointer<int> ptr2(ptr1);
        // 赋值
        // UniquePointer<int> ptr3(nullptr);
        // ptr3 = ptr1;

        throw std::bad_alloc();
        delete pa;
    }
    catch (const std::exception &except)
    {
        std::cout << except.what() << std::endl;
    }
}

// 但是在某些场景下我们需要智能指针可以进行拷贝
// 所以引入了shared_ptr，底层采用的是引用计数
// shared_ptr
void Test5()
{
    try
    {
        Student *pa = new Student;
        SharedPointer<Student> ptr1(pa);
        // 这里我们通过引用计数实现了shared_ptr
        SharedPointer<Student> ptr2(ptr1);
        SharedPointer<Student> ptr3(nullptr);
        ptr3 = ptr2;

        throw std::bad_alloc();
    }
    catch (const std::exception &except)
    {
        std::cout << except.what() << std::endl;
    }
}

// 但是这里还存在一些问题？
// 库里的智能指针是线程安全的，我们需要实现线程安全的智能指针
// 而unique_ptr本身就是线程安全的，我们需要考虑的是shared_ptr
// shared_ptr中的引用计数涉及到了线程安全，虽然资源指针可能也涉及到了线程安全
// 但是_ptr的线程安全不是智能指针应该处理的，这部分的内容应该交付给使用的程序员
void Func(int num, SharedPointer<Student> &ptr)
{
    for (int i = 0; i < num; i++)
    {
        SharedPointer<Student> tmp(ptr);
    }
}

// 存在线程安全问题
void Test6()
{
    int num = 100000000;
    SharedPointer<Student> ptr(new Student);
    // 创建两个线程分别去创建ptr拷贝(然后可以最后观察count数值)
    std::thread t1(Func, num, std::ref(ptr));
    std::thread t2(Func, num, std::ref(ptr));

    t1.join();
    t2.join();

    std::cout << ptr.use_count() << std::endl;

    // 所以我们需要进行加锁操作
}

// weak_ptr循环引用
void Test7()
{
    // 假设有这样一个场景：
    // ListNode* node1 = new ListNode;
    // ListNode* node2 = new ListNode;
    // node1->_next = node2;
    // node2->_prev = node1;

    // delete node1;
    // delete node2;
    // node1 = nullptr;
    // node2 = nullptr;

    // 如果我希望使用智能指针来管理资源呢？
    SharedPointer<ListNode> ptr1(new ListNode);
    SharedPointer<ListNode> ptr2(new ListNode);

    std::cout << "ptr1: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2: " << ptr2.use_count() << std::endl;

    ptr1->_next = ptr2;
    ptr2->_prev = ptr1;
    ptr1->_prev = ptr2;
    ptr2->_next = ptr1;

    std::cout << "ptr1: " << ptr1.use_count() << std::endl;
    std::cout << "ptr2: " << ptr2.use_count() << std::endl;
}

// 定制删除器
template <typename T>
struct DeleteArray
{
public:
    void operator()(T *array)
    {
        delete[] array;
    }
};

void Test8()
{
    std::mutex mtx;
    // lambda表达式
    std::shared_ptr<std::mutex> ptr1(&mtx, [](std::mutex *_pmtx)
                                     { _pmtx->unlock(); });
    std::shared_ptr<int> ptr2(new int[10], [](int *pa)
                              { delete[] pa; });

    // 仿函数
    std::shared_ptr<Student> ptr3(new Student[10], DeleteArray<Student>());
}

// 四种类型转换
class Base
{
public:
    virtual ~Base()
    {
    }

protected:
private:
};

class Derived : public Base
{
public:
protected:
private:
};

void Test9()
{
    // 类型转换包括了：隐式类型转化和强制类型转换
    // 其中隐式类型转换是一个潜藏的巨大隐患,很多时候会给我们带来意想不到的坑
    // 关于强制类型转化,推荐使用C++11推出四种规范的类型转换
    // static_cast<T>(),用于两个相关的类型相互转化
    int num1 = 10;
    double num2 = 10.1;
    int num3 = static_cast<int>(num2);
    std::cout << "num3: " << num3 << std::endl;

    // dynamic_cast<T>(),用于将父类的指针/引用转化成子类的指针/引用
    // 向上转换：子类指针/引用转换成父类指针/引用(这并不是类型转换,这属于切片、截断)
    // 向下转换：父类指针/引用转换成子类指针/引用(类型转换)
    Base *pb = new Base;
    Derived *pd = new Derived;
    Base *pb1 = pd;
    // 使用的时候,父类必须包含虚函数
    // 成功的时候直接转换,不成功的时候返回0
    Derived *pd1 = dynamic_cast<Derived *>(pb);

    delete pd, pb;
    pb = nullptr;
    pd = nullptr;

    // reinterpret_cast<T>(),用于两个不相关的类型相互转化
    int a = 10;
    long b = reinterpret_cast<long>(&a);
    std::cout << "&a: " << &a << std::endl;
    std::cout << "b: " << b << std::endl;

    // const_cast<T>(),取消常量属性(常用来赋值)
    // 注意：const_cast中的类型必须是指针、引用或者是指向对象类型成员的指针
    const char *str = "hello";
    char *copy = const_cast<char *>(str);
    std::cout << "copy: " << copy << std::endl;

    // 测试
    const int tmp = 10;
    int *ptr = const_cast<int *>(&tmp);

    *ptr = 20;
    std::cout << "tmp: " << tmp << std::endl;
    std::cout << "*ptr: " << *ptr << std::endl;
    // 存在编译器优化,使用volatile
    volatile const int tmp1 = 20;
    int *ptr1 = const_cast<int *>(&tmp1);

    *ptr1 = 10;
    std::cout << "tmp1: " << tmp1 << std::endl;
    std::cout << "*ptr1: " << *ptr1 << std::endl;
}

int main()
{
    // Test1();
    // Test2();
    // Test3();
    // Test4();
    // Test5();
    // Test6();
    // Test7();
    // Test8();
    Test9();
    return 0;
}